using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using MacomberMap.Common.User_Interfaces.Components;
using System.ComponentModel;
using System.Windows.Forms;
using System.Drawing;
using MacomberMap.Common.User_Interfaces.One_Lines.Elements;
using MacomberMap.Common.Components;
using MacomberMap.Common.Database_Interface;
using System.Drawing.Drawing2D;
using MacomberMap.Common.Types;
using MacomberMap.Common.Convertors;
using System.Reflection;
using MacomberMap.Common.Internals;

namespace MacomberMap.Common.User_Interfaces.One_Lines.Elements
{
    /// <summary>
    /// This class holds information on a node, which is an aggregate of poke pints and connected elements
    /// </summary>
    public class MM_OneLine_Node : MM_OneLine_PokePoint
    {   
        #region Variable declarations
        /// <summary>The collection of elements and our sub-points to which they're connected</summary>
        private Dictionary<MM_OneLine_Element, MM_OneLine_PokePoint[]> _ConnectionPoints =new Dictionary<MM_OneLine_Element,MM_OneLine_PokePoint[]>(10);

        /// <summary>The collection of elements and our sub-points to which they're connected</summary>
        public Dictionary<MM_OneLine_Element, MM_OneLine_PokePoint[]> ConnectionPoints
        {
            get { return _ConnectionPoints; }          
        }
        
        /// <summary>The graphics path for a series of points</summary>        
        private Dictionary<MM_OneLine_Element, GraphicsPath> _GraphicsPaths = new Dictionary<MM_OneLine_Element,GraphicsPath>(10);

        /// <summary>The graphics path for a series of points</summary>        
        public Dictionary<MM_OneLine_Element, GraphicsPath> GraphicsPaths
        {
            get { return _GraphicsPaths; }
        }

        /// <summary>
        /// The resource node name, if any
        /// </summary>
        private String _ResourceNode=null;

        /// <summary>
        /// The name of the resource node
        /// </summary>
        [Category("Display"), Description("The name of the resource node")]
        public String ResourceNode
        {
            get { return _ResourceNode; }
            set { _ResourceNode = value; }
        }

        /// <summary>The TEID for the busbar section component, if any</summary>
        private UInt64 _BusbarSection;

        /// <summary>The TEID for the busbar section component, if any</summary>
        [Category("MacomberMap"), Description("The TEID for the electrical bus component, if any"), DefaultValue(0)]
        public UInt64 BusbarSection
        {
            get { return _BusbarSection; }
            set { _BusbarSection = value;  }
        }

        #endregion

        #region Initialization
        /// <summary>
        /// Initialize a new viewable Node
        /// </summary>
        /// <param name="BaseElement">The element on which the node is based</param>
        /// <param name="DisplayParameters">The display parameters</param>
        /// <param name="Orientation">The orientation of the node</param>
        public MM_OneLine_Node(MM_Element BaseElement, MM_OneLine_Display DisplayParameters, enumOrientations Orientation)
            : base(BaseElement, DisplayParameters, Orientation)
        {
            this.BaseElement = BaseElement;
            this.BaseNode = this;
        }

        /// <summary>
        /// Initialize a new node with just display parameters
        /// </summary>
        /// <param name="DisplayParameters"></param>
        public MM_OneLine_Node(MM_OneLine_Display DisplayParameters)
            : base(DisplayParameters)
        {
            this.BaseNode = this;
        }


        /// <summary>
        /// Initialize a new viewable Node
        /// </summary>
        /// <param name="ElemConfig">The Xml configuration for the Node</param>
        /// <param name="DisplayParameters">The display parameters for the Node</param>
        /// <param name="DbConnection">The database connection</param>
        /// <param name="OLView">The one-line viewer</param>
        /// <param name="Elements">The elements on the display</param>
        public MM_OneLine_Node(XmlElement ElemConfig, MM_OneLine_Display DisplayParameters, MM_Database DbConnection, Dictionary<MM_Element, MM_OneLine_Element> Elements, MM_OneLine_Viewer OLView)
            : base(ElemConfig, DisplayParameters, DbConnection)
        {
            List<MM_OneLine_Element> NewElems = new List<MM_OneLine_Element>();
            this.BaseNode = this;


            //First, assign our base linkages
            MM_Serializable.ReadXml(ElemConfig, this);

            //Now, pull our list of poke points through
            foreach (XmlElement xLinkage in ElemConfig.ChildNodes)
                if (xLinkage.Name == "Descriptor")
                {
                    this.Descriptor = new MM_OneLine_Descriptor(this, xLinkage, DisplayParameters);
                    NewElems.Add(Descriptor);
                }
                else if (xLinkage.Name == "SecondaryDescriptor")
                {
                    this.SecondaryDescriptor = new MM_OneLine_SecondaryDescriptor(this, xLinkage, DisplayParameters);
                    NewElems.Add(SecondaryDescriptor);
                }
                else
                {

                    MM_OneLine_Element TargetElement = null;
                    foreach (MM_OneLine_Element Elem in OLView.DisplayElements.Values)
                        if (Elem.TEID == Convert.ToUInt64(xLinkage.Attributes["TEID"].Value))
                            TargetElement = Elem;

                    List<MM_OneLine_PokePoint> Pokes = new List<MM_OneLine_PokePoint>();
                    Pokes.Add(this);
                    foreach (XmlElement xPoke in xLinkage.ChildNodes)
                        if (xPoke.Name == "PricingVector")
                        {
                            MM_OneLine_PricingVector PV = new MM_OneLine_PricingVector(xPoke, this, DisplayParameters);
                            Pokes.Add(PV);
                            if (xPoke["Descriptor"] != null)
                                PV.Descriptor = new MM_OneLine_Descriptor(PV, xPoke["Descriptor"], DisplayParameters);
                            if (xPoke["SecondaryDescriptor"] != null)
                                PV.SecondaryDescriptor = new MM_OneLine_SecondaryDescriptor(PV, xPoke["SecondaryDescriptor"], DisplayParameters);

                            NewElems.Add(PV);
                        }
                        else
                        {
                            Rectangle PointLocation = (Rectangle)MM_Serializable.RetrieveConvertedValue(typeof(Rectangle), xPoke.Attributes["Bounds"].Value);
                            MM_OneLine_Element.enumOrientations Orientation = MM_OneLine_Element.enumOrientations.Unknown;
                            if (xPoke.HasAttribute("Orientation"))
                                Orientation = (MM_OneLine_Element.enumOrientations)MM_Serializable.RetrieveConvertedValue(typeof(MM_OneLine_Element.enumOrientations), xPoke.Attributes["Orientation"].Value);
                            MM_OneLine_PokePoint NewPoke = OLView.LocatePoke(PointLocation, this, DisplayParameters, this.BaseElement, Orientation, Convert.ToBoolean(xPoke.Attributes["IsJumper"].Value), Convert.ToBoolean(xPoke.Attributes["IsVisible"].Value));
                            MM_Serializable.ReadXml(xPoke, NewPoke);
                            if (xPoke["Descriptor"] != null && NewPoke.Descriptor == null)
                            {
                                NewPoke.Descriptor = new MM_OneLine_Descriptor(NewPoke, xPoke["Descriptor"], DisplayParameters);
                                NewElems.Add(NewPoke.Descriptor);
                            }
                            if (xPoke["SecondaryDescriptor"] != null && NewPoke.SecondaryDescriptor == null)
                            {
                                NewPoke.SecondaryDescriptor = new MM_OneLine_SecondaryDescriptor(NewPoke, xPoke["SecondaryDescriptor"], DisplayParameters);
                                NewElems.Add(NewPoke.SecondaryDescriptor);
                            }
                            Pokes.Add(NewPoke);
                        }
                    ConnectionPoints.Add(TargetElement, Pokes.ToArray());
                }
            DisplayParameters.HandleAddedElements(NewElems.ToArray());
            RebuildPaths(OLView.PanelScrollPosition);                
        }

        /// <summary>
        /// Rebuild the graphics paths for the elements
        /// </summary>
        public void RebuildPaths(Point OffsetPoint)
        {

            GraphicsPaths.Clear();
            foreach (KeyValuePair<MM_OneLine_Element, MM_OneLine_PokePoint[]> kvp in ConnectionPoints)
            {
                GraphicsPath g = new GraphicsPath();


                for (int a = 0; a < kvp.Value.Length; a++)
                {
                    if (kvp.Value[a] == this)
                    { }

                    //if (!kvp.Value[a].IsJumper && kvp.Value[a].IsVisible) { }
                    /*else if (kvp.Value[a].IsVisible)
                        g.AddRectangle(OffsetRectangle(kvp.Value[a].Bounds, OffsetPoint));
                    */
                    else if (a == 1 && kvp.Value[a - 1].IsJumper)
                        g.AddLine(CenterRect(MM_OneLine_Node.OffsetRectangle(kvp.Value[a - 1].Bounds, OffsetPoint)), CenterRect(MM_OneLine_Node.OffsetRectangle(kvp.Value[a].Bounds, OffsetPoint)));
                    else if (a > 0 && kvp.Value[a - 1].IsJumper == false)
                        AddStraightLine(g, OffsetPoint, kvp.Value[a - 1], kvp.Value[a]);
                                                                                                                          
                }

                //Given that we're a busbar section, add the line from poke to element, or busbar to element
                if (kvp.Value.Length == 0)
                    AddStraightLine(g, Point.Empty, this, kvp.Key);                    
                else
                    AddStraightLine(g, OffsetPoint, kvp.Value[kvp.Value.Length - 1], kvp.Key);                    
                GraphicsPaths.Add(kvp.Key, g);
            }
        }

       

        

        /// <summary>
        /// Offset a rectangle by a set point
        /// </summary>
        /// <param name="InRect">The original rectangle</param>
        /// <param name="OffsetPoint">The offset point</param>
        /// <returns></returns>
        public static Rectangle OffsetRectangle(Rectangle InRect, Point OffsetPoint)
        {
            return new Rectangle(InRect.Left - OffsetPoint.X, InRect.Top - OffsetPoint.Y, InRect.Width, InRect.Height);
        }
        #endregion


        #region Rendering
      

        /// <summary>
        /// Draw connecting lines between this node and its other components
        /// </summary>
        /// <param name="g">The graphics connector</param>
        public void DrawConnectingLines(Graphics g)
        {
            foreach (KeyValuePair<MM_OneLine_Element, GraphicsPath> kvp in GraphicsPaths)
            {
                Color ForeColor;
                if (kvp.Key.DisplayParameters.DDLImportMode)
                    ForeColor = Color.DarkGreen;
                else if (BaseElement.KVLevel != null)
                    ForeColor = BaseElement.KVLevel.Energized.ForeColor;
                else if (kvp.Key.KVLevel != null)
                    ForeColor = kvp.Key.KVLevel.Energized.ForeColor;
                else if (ConnectionPoints[kvp.Key][ConnectionPoints[kvp.Key].Length - 1].KVLevel != null)
                    ForeColor = ConnectionPoints[kvp.Key][ConnectionPoints[kvp.Key].Length - 1].KVLevel.Energized.ForeColor;
                else
                    ForeColor = Color.DarkRed;
                using (Pen NewPen = new Pen(ForeColor, 1))
                    g.DrawPath(NewPen, kvp.Value);
            }
        }

        /// <summary>
        /// Determine the point at which the node point exists
        /// </summary>
        /// <param name="TargetPoint"></param>
        /// <returns></returns>
        Point NodeExitPoint(Point TargetPoint)
        {
            Point StartingPoint = CenterRect(Bounds);
            if (TargetPoint.X < Left)
                StartingPoint.X = Left;
            else if (TargetPoint.X > Right)
                StartingPoint.X = Right;
            else
                StartingPoint.X = TargetPoint.X;
            if (TargetPoint.Y < Top)
                StartingPoint.Y = Top;
            else if (TargetPoint.Y > Bottom)
                StartingPoint.Y = Bottom;
            else
                StartingPoint.Y = TargetPoint.Y;
            return StartingPoint;
        }

        /// <summary>
        /// Add a straight line between two rectangles
        /// </summary>
        /// <param name="g">The graphics path</param>
        /// <param name="OffsetPoint">The offset point</param>
        /// <param name="FromElement">The incoming rectangle</param>
        /// <param name="ToElement">The outgoing rectangle</param>
        public static void AddStraightLine(GraphicsPath g, Point OffsetPoint, MM_OneLine_Element FromElement, MM_OneLine_Element ToElement)
        {
            //Determine our original centers
            Rectangle FromRect = FromElement.Bounds;
            Rectangle ToRect = ToElement.Bounds;

            FromRect.X -= OffsetPoint.X;
            FromRect.Y-= OffsetPoint.Y;
            ToRect.X -= OffsetPoint.X;
            ToRect.Y-= OffsetPoint.Y;
            Point Center1 = CenterRect(FromRect);
            Point Center2 = CenterRect(ToRect);

          
                //Adjust the first point if need be

            if (FromElement is MM_OneLine_Node)
            {
                if (Center2.X >= FromRect.Left && Center2.X <= FromRect.Right)
                    Center1.X = Center2.X;
                else if (Center2.X > FromRect.Right)
                    Center1.X = FromRect.Right;
                else if (Center2.X < FromRect.Left)
                    Center1.X = FromRect.Left;


                if (Center2.Y >= FromRect.Top && Center2.Y <= FromRect.Bottom)
                    Center1.Y = Center2.Y;
                else if (Center2.Y < FromRect.Top)
                    Center1.Y = FromRect.Top;
                else if (Center2.Y > FromRect.Bottom)
                    Center1.Y = FromRect.Bottom;
            }

                //Adjust the second point if need be
            if (ToElement is MM_OneLine_Node)
            {
                if (Center1.X >= FromRect.Left && Center1.X <= FromRect.Right)
                    Center2.X = Center1.X;
                else if (Center1.X > FromRect.Right)
                    Center2.X = FromRect.Right;
                else if (Center1.X < FromRect.Left)
                    Center2.X = FromRect.Left;

                    if (Center1.Y >= FromRect.Top && Center1.Y <= FromRect.Bottom)
                        Center2.Y = Center1.Y;
                    else if (Center1.Y < FromRect.Top)
                        Center2.Y = FromRect.Top;
                    else if (Center1.Y > FromRect.Bottom)
                        Center2.Y = FromRect.Bottom;
            } 
            
            g.AddLine(Center1, Center2);
        }



     

        #endregion

    }
}
